function twitterList() {
    var prefs = new gadgets.Prefs();
    
    this.query = gadgets.util.unescapeString(prefs.getString("query"));
    this.tweetTime = prefs.getInt("tweetTime") > 5 ? prefs.getInt("tweetTime") * 1000 : 5000;
    this.tweetNumber = prefs.getInt("tweetNumber") > 5 ? prefs.getInt("tweetNumber") : 5;
    this.showAvatar = prefs.getBool("tweetAvatar");
    this.avatarSize = prefs.getString("avatarSize");
    this.showTimestamps = prefs.getBool("showTimestamps");
    this.showHashtags = prefs.getBool("showHashtags");
    this.showTopTweets = prefs.getBool("showTopTweets");
    this.linkColor = prefs.getString("linkColor") != "" ? prefs.getString("linkColor") : "#0A5DC3";					
    this.enableLinks = prefs.getBool("enableLinks");
    this.scrollDirection = prefs.getString("scrollDirection");
    this.showSeparator = prefs.getBool("showSeparator");
    this.separatorWidth = prefs.getInt("separatorWidth");
    this.separatorColor = prefs.getString("separatorColor");
    this.width = prefs.getString("rsW");
    this.height = prefs.getString("rsH");
    
    this.sinceId = -1;
    this.results = [];
    this.newResults = [];
    this.scrollCount = 0;
    this.updateInterval = 60000;
    this.updatesTimerExpired = false;
}
twitterList.prototype.loadTweets = function() {		
    var params = {},
	self = this,
	showSince = "";
    
    if (this.sinceId != -1) {
	showSince = "&since_id=" + this.sinceId + "&refresh=true";
    }
    
    params[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.JSON;
    params[gadgets.io.RequestParameters.REFRESH_INTERVAL] = 60;
    gadgets.io.makeRequest("http://search.twitter.com/search.json?q=" + encodeURIComponent(this.query) + "&include_rts=true&rpp=" + this.tweetNumber + showSince,
	function(response) {
	    if (response.error) {
		console.log(response.error);
	    }
	    else if (response.data.results && response.data.results.length > 0) {
		var newResults = response.data.results;
		
		self.newResults = [];
		self.newResults.push.apply(self.newResults, newResults);
		self.newResults.sort(function(a, b) {
		    self.sort(a, b);
		});
		
		self.sinceId = response.data.max_id_str;
		self.results.unshift.apply(self.results, newResults);	//Add new tweets to the beginning of the array.
		self.results = self.results.slice(0, self.tweetNumber);	//Remove oldest tweets.
		
		//This is an update.
		if (self.updatesTimerExpired) {
		    self.scrollCount = self.newResults.length == self.results.length ? 0 : self.newResults.length;
		    self.updatesTimerExpired = false;
		}
		else {
		    $("#scrollContainer").infiniteScroll({
			direction: self.scrollDirection,
			duration: self.tweetTime,
			cloneItem: function() {
			    self.cloneItem.call(self);
			}
			})
			.bind("onScroll", function(event) {
			    self.onScroll.call(self);
		    });
		}		
				
		if (self.newResults.length > 0) {
		    //Remove first tweet that may have been cloned before update occurred (depends on timing).
		    if ($(".tweet").length > self.results.length) {
			if (self.scrollDirection == "down") {
			    $(".tweet:first").remove();
			}
			else {
			    $(".tweet:last").remove();
			}
		    }
		    		    
		    //Flag old tweets with the delete class so they can be removed after scrolling off.
		    if (self.scrollDirection == "down") {
			$(".tweet").slice(0, self.newResults.length).addClass("delete");
		    }
		    else {
			$(".tweet").slice(self.results.length - self.newResults.length).addClass("delete");
		    }
		}
		
		self.createTweets();		
	    }
	    
	    readyEvent();
	}
    , params);
}
twitterList.prototype.sort = function(a, b) {
    var getDater = function(dateString) {
	return new Date(dateString).getTime();
    };
		
    if (getDater(a.created_at) > getDater(b.created_at)) {
	return -1;
    }
    else if (getDater(a.created_at) < getDater(b.created_at)) {
	return 1;
    }
    else {
	return 0;
    }
}
twitterList.prototype.createTweets = function() {
    for (var i = 0; i < this.newResults.length; i++) {
	this.createTweet(this.newResults[i]);
    }
    
    //Duplicate all tweets until Placeholder is filled.
    while ($(".page").height() < $(".tweets").height()) {
	if (this.scrollDirection == "up") {
	    $(".page").append($(".tweet").clone());
	}
	else {
	    $(".page").prepend($(".tweet").clone());
	}
    }
}
twitterList.prototype.createTweet = function(result) {
    var tweet = new Object();
	
    tweet.id = result.id;
    tweet.user = result.from_user;
    tweet.tweet = this.clean(result.text);
    
    if (this.showAvatar) {
	if (this.avatarSize == "large") {
	    tweet.avatar = result.profile_image_url.replace(/_normal\./, '_bigger.');
	}
	else {
	    tweet.avatar = result.profile_image_url;
	}
    }
    
    if (this.showTimestamps) {
	tweet.createdAt = this.timeAgo(result.created_at);
    }
    
    tweet.metadata = result.metadata;
    this.tweet = new Tweet(tweet, {
	showAvatar: this.showAvatar,
	avatarSize: this.avatarSize,
	enableLinks: this.enableLinks,
	showTimestamps: this.showTimestamps,
	showHashtags: this.showHashtags,
	showTopTweets: this.showTopTweets,
	showSeparator: this.showSeparator,
	scrollDirection: this.scrollDirection
    });
}
twitterList.prototype.cloneItem = function() {
    //Only clone the tweet if it has not been flagged for deletion.
    if (this.scrollDirection == "down") {
	if (!$(".tweet:last").hasClass("delete")) {
	    this.createTweet(this.results[this.scrollCount]);
	}
	else {
	    //Adjust scrollCount since we are not cloning a tweet and the scrollCount will be incremented
	    //on the next scroll, which would give us an off-by-one error.
	    this.scrollCount--;
	}
    }
    else {
	if (!$(".tweet:first").hasClass("delete")) {
	    this.createTweet(this.results[this.scrollCount]);
	}
	else {
	    //Adjust scrollCount since we are not cloning a tweet and the scrollCount will be incremented
	    //on the next scroll, which would give us an off-by-one error.
	    this.scrollCount--;
	}
    }
}
twitterList.prototype.onScroll = function() {
    this.scrollCount++;
    
    //All tweets have been scrolled through.
    if (this.scrollCount == this.results.length) {
	this.scrollCount = 0;
	
	//Check for new tweets.
	if (this.updatesTimerExpired) {
	    this.loadTweets();
	}
    }
}
twitterList.prototype.play = function() {
    var self = this;
    
    if ($("#scrollContainer").infiniteScroll.start) {
	$("#scrollContainer").infiniteScroll.start();
    }
    
    setInterval(function() {
	self.expireUpdatesTimer();
    }, this.updateInterval);
}
twitterList.prototype.pause = function() {
    if ($("#scrollContainer").infiniteScroll.pause) {
	$("#scrollContainer").infiniteScroll.pause();
    }
}
twitterList.prototype.expireUpdatesTimer = function() {
    this.updatesTimerExpired = true;
}
twitterList.prototype.timeAgo = function(dateString) {
    var rightNow = new Date(),
	then = new Date(dateString),
	diff = rightNow - then,
	second = 1000,
	minute = second * 60,
	hour = minute * 60,
	day = hour * 24,
	week = day * 7;
  
    if (isNaN(diff) || diff < 0) {
	return "";	//Return blank string if unknown
    }
  
    if (diff < second * 2) {
	//Within 2 seconds.
	return "right now";
    }
  
    if (diff < minute) {
	return Math.floor(diff / second) + " seconds ago";
    }
  
    if (diff < minute * 2) {
	return "about 1 minute ago";
    }
  
    if (diff < hour) {
	return Math.floor(diff / minute) + " minutes ago";
    }
  
    if (diff < hour * 2) {
	return "about 1 hour ago";
    }
  
    if (diff < day) {
	return  Math.floor(diff / hour) + " hours ago";
    }
    
    if (diff > day && diff < day * 2) {
	return "yesterday";
    }
    
    if (diff < day * 365) {
      return Math.floor(diff / day) + " days ago";
    }
    else {
	return "over a year ago";
    }
}
twitterList.prototype.link = function(tweet) {
    var self = this;
    
    return tweet.replace(/\b(((https*\:\/\/)|www\.)[^\"\']+?)(([!?,.\)]+)?(\s|$))/g, function(link, m1, m2, m3, m4) {
      var http = m2.match(/w/) ? 'http://' : '';
      var linkText = '';
      
      if (self.enableLinks) { 
	      linkText = ' target="_blank" href="' + http + m1 + '"';
      }
      
      return '<a class="hyperlink"' + linkText + '>' + ((m1.length > 25) ? m1.substr(0, 24) + '...' : m1) + '</a>' + m4;
    });
}

twitterList.prototype.at = function(tweet) {
    var self = this;
    
    return tweet.replace(/\B[@＠]([a-zA-Z0-9_]{1,20})/g, function(m, username) {
	var linkText = '';
	
	if (self.enableLinks) { 
	      linkText = ' target="_blank" href="http://twitter.com/' + username + '"';
	}
	
	return '@<a class="atreply"' + linkText + '>' + username + '</a>';
    });
}
twitterList.prototype.list = function(tweet) {
    var self = this;
    
    return tweet.replace(/\B[@＠]([a-zA-Z0-9_]{1,20}\/\w+)/g, function(m, userlist) {
	var linkText = '';
	
	if (self.enableLinks) { 
	      linkText = ' target="_blank" href="http://twitter.com/' + userlist + '"';
	}
	
	return '@<a class="atreply"' + linkText + '>' + userlist + '</a>';
    });
}
twitterList.prototype.hash = function(tweet) {
    var self = this;
    
    return tweet.replace(/(^|\s+)#(\w+)/gi, function(m, before, hash) {
	var linkText = '';
	
	if (self.enableLinks) { 
	      linkText = ' target="_blank" href="http://twitter.com/search?q=%23' + hash + '"';
	}
	
	return before + '<a class="hashtag"' + linkText + '>#' + hash + '</a>';
    });
}
twitterList.prototype.clean = function(tweet) {
    return this.hash(this.at(this.list(this.link(tweet))));
}
function Tweet(tweet, settings) {
    var $tweet = $("<div class='tweet item'>"),
	$tweetAvatar = $("<div class='tweetAvatar'>"),
	$avatar = $("<img>"),
	$tweetText = $("<div class='tweet_font-style'>"),
	$text = $("<h3 class='text'>"),
	$date = $("<p class='date'>"),
	$user;
	    
    if (settings.showAvatar) {
	if (settings.avatarSize == "large") {
	    $avatar.addClass("avatar");
	    $avatar.attr("src", tweet.avatar);
	    $tweetText.addClass("tweetText");
	}
	else {
	    $avatar.addClass("smallAvatar");
	    $avatar.attr("src", tweet.avatar);
	}
	
	$tweetAvatar.append($avatar);
	$tweet.append($tweetAvatar);
    }
    
    if (settings.enableLinks) {
	$user = "<h4><a target='_blank' href='http://twitter.com/" + tweet.user + "'>" + tweet.user + "</a></h4>";
    }
    else {
	$user = $("<h4>").html(tweet.user);
    }
    
    $tweetText.append($user);
    $text.html(tweet.tweet);
    $tweetText.append($text);
    
    if (settings.showTimestamps) {
	$date.html(tweet.createdAt);
	$tweetText.append($date);
    }
    
    if (settings.showTopTweets) {
	if (tweet.metadata && tweet.metadata.result_type && tweet.metadata.result_type == 'popular') {
	    $tweetText.append("<span class='popular'>" + tweet.metadata.recent_retweets + "+ recent retweets");
	}
    }
    
    $tweet.append($tweetText);
    
    if (settings.showSeparator) {
	$tweet.addClass("separator");
    }
    
    if (settings.scrollDirection == "up") {
	$(".page").append($tweet);
    }
    else {
	$(".page").prepend($tweet);
    }
    
    //Issue 620 - Hide hash tags after tweet has been added to the DOM.
    if (!settings.showHashtags) {
	$(".hashtag").hide();
    }
}